[REQ-001] Add currentStreak and bestStreak pure functions with unit tests#3
Conversation
Implements REQ-001 streak logic in src/habits.ts (pure, no DOM/storage). Adds 12 unit tests covering empty habits, single days, gaps, active runs, and best-past-run scenarios. Closes #1
markoub
left a comment
There was a problem hiding this comment.
Review — PR #3: Add currentStreak and bestStreak
This is a clean, well-structured implementation. All acceptance criteria from issue #1 are met, CI is green, and the code is genuinely pleasant to read. Here's the full walkthrough.
Correctness ✅
currentStreak (src/habits.ts:49–67) — logic is correct in all required cases:
- Starts from
today; if not in the set, falls back one day; if still not found, returns 0 immediately. No infinite loop risk. - The backwards
whilewalk correctly counts the contiguous tail. Traced manually through all 6 test scenarios — all produce the right result.
bestStreak (src/habits.ts:74–88) — also correct:
- The early-return guard for empty completions is clean.
- The
[...habit.completions].sort()defensive copy is good hygiene even thoughtoggleCompletionalready sorts; costs nothing. - The in-place
best/runscan is O(n) and readable. Confirmed it handles the single-element case (loop body never executes, returnsbest = 1) and the "past best longer than current run" case correctly.
shiftDay (src/habits.ts:48–53) — the UTC anchoring ("T00:00:00Z" + setUTCDate) is exactly right to avoid DST surprises. Good call.
Purity ✅
src/habits.ts remains completely free of DOM, localStorage, window, and clock reads. today is always injected as a YYYY-MM-DD string parameter. shiftDay uses Date only for arithmetic, never to read wall-clock time. The purity invariant holds.
Test coverage ✅
All six cases required by the acceptance criteria are present for both functions:
| Case | currentStreak |
bestStreak |
|---|---|---|
| Empty habit → 0 | ✅ | ✅ |
| Single day today → 1 | ✅ | ✅ |
| Single day yesterday, today not done | ✅ | ✅ |
| Streak broken in the middle | ✅ | ✅ |
| Active streak of several days ending today | ✅ (3-day + 6-day) | ✅ (6-day) |
| Best streak is a past run | n/a | ✅ |
| Gap two days ago → 0 for current | ✅ | n/a |
12 tests total, all meaningful and non-redundant.
Style & scope ✅
- Single focused commit
feat: add currentStreak and bestStreak pure functions— conventional commit format, good body,Closes #1wired up correctly. - Diff touches only
src/habits.tsandsrc/habits.test.ts. No stray changes. - Immutable patterns consistent with the rest of the codebase.
Non-blocking nit
The JSDoc blocks on currentStreak and bestStreak are multi-line (3 lines and 2 lines respectively), which is a minor departure from CLAUDE.md's "one short line max" comment guideline. The content is genuinely useful for exported API functions, so this is not a blocker — just worth keeping in mind for future contributions.
Verdict: ready to merge. CI passed, all acceptance criteria satisfied, purity maintained, edge cases covered. Tagging ready-to-merge and squash-merging now.
Generated by Claude Code
Summary
currentStreak(habit, today)tosrc/habits.ts: counts consecutive completed days ending on or just beforetoday; returns 0 if neither today nor yesterday is completed.bestStreak(habit)tosrc/habits.ts: returns the length of the longest ever consecutive run of completions; returns 0 for an empty habit.localStorage, noDatereads —todayis always passed as a parameter).shiftDayhelper for date arithmetic using UTC to avoid DST issues.How I tested
Ran
npm ci && npm test && npm run build— all 17 tests pass, build succeeds with no TypeScript errors.New test cases added to
src/habits.test.ts:currentStreak= 1,bestStreak= 1currentStreak; best segment forbestStreakCloses #1
Generated by Claude Code